package mn.idax.exchange.otc.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.core.type.TypeReference;
import mn.idax.exchange.common.CalendarUtil;
import mn.idax.exchange.common.UTCTimeUtil;
import mn.idax.exchange.otc.domain.capital.*;
import mn.idax.exchange.otc.entity.RequestLog;
import mn.idax.exchange.otc.enums.CapitalAPIEnum;
import mn.idax.exchange.otc.enums.RequestLogEnum;
import mn.idax.exchange.otc.enums.response.SystemStatusResponseEnum;
import mn.idax.exchange.otc.exception.IdaxException;
import mn.idax.exchange.otc.mapper.RequestLogMapper;
import mn.idax.exchange.otc.util.JsonUtil;
import mn.idax.exchange.otc.util.LogUtils;
import mn.idax.exchange.otc.util.OkHttpClientUtils;
import mn.idax.exchange.otc.util.UUidUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

/**
 * 资金接口共用
 *
 * @author .shouhua
 * @time 2018.11.21
 */
@Component
public class CapitalAPIBaseService {

    /**
     * Guid key
     */
    public static final String GUID_KEY = "SERIAL:NUMBER:JAVA:OTC";
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private RequestLogMapper requestLogMapper;
    @Resource
    private DataSourceTransactionManager transactionManager;
    @Value("${capital.API}")
    private String capitalAPI;
    @Autowired
    private AccountCapitalService accountCapitalService;

    public Map<String, Object> requestAPI(String api, Object requestParam) {
        LogUtils.info(this.getClass(), "otcApiLogRequestAPI call remote:" + capitalAPI + api + "request is " + JSON.toJSONString(requestParam));
        return OkHttpClientUtils.post(capitalAPI + api, requestParam, new TypeReference<Map<String, Object>>() {
        });
    }

    public RequestLog getRequestLog(Integer txId, Integer sourceId) {

        RequestLog requestLog = new RequestLog();
        requestLog.setTxId(txId);
        requestLog.setSourceId(sourceId);
        List<RequestLog> list = requestLogMapper.selectRequestLog(requestLog);

        return list.isEmpty() ? null : list.get(0);
    }

    private int updateStatusAndCreateTime(Integer txId, Integer sourceId) {
        try {

            RequestLog requestLog = this.getRequestLog(txId, sourceId);

            if (requestLog != null && (requestLog.getStatus().intValue() == 2 || requestLog.getStatus().intValue() == 3)) {
                requestLog.setCreateTime(new Date(UTCTimeUtil.getUTCTimeLong()));
                requestLog.setStatus(0);
                int result = requestLogMapper.updateCreateTimeAndStatus(requestLog);
                if (result == 1) {
                    //update success
                    return 1;
                }
            } else {
                //not found
                return 0;
            }
        } catch (Exception e) {
            LogUtils.error(this.getClass(), "updateStatusAndCreateTime error:" + txId.toString() + "," + sourceId.toString() + ExceptionUtils.getStackTrace(e));
        }
        return -1;
    }

    public Boolean compenstateRequestAPI(Long guid, CapitalAPIEnum capitalAPIEnum, String requestJson, String reverseJson, Integer sourceId) {
        LogUtils.info(this.getClass(), "otcpost request is:" + requestJson);

        int result = updateStatusAndCreateTime(capitalAPIEnum.getTxId(), sourceId);

        if (result == -1) {
            return false;
        }

        if (result == 0) {
            transactionSubmit(s -> initRequestLog(guid, capitalAPIEnum, requestJson, reverseJson, sourceId));
        }

        Map<String, Object> post = null;
        final int[] update = {0};

        try {
            post = requestAPI(capitalAPIEnum.getApiUrl(), requestJson);
            LogUtils.info(this.getClass(), "otcpost response is:" + JSON.toJSONString(post));
        } catch (Exception ex) {
            //call remote error, update request log, set status = 9
            transactionSubmit(s -> {
                RequestLog updateRquestLog = RequestLog.builder()
                        .GUID(guid)
                        .responseJson(null)
                        .updateTime(CalendarUtil.getUTCDate().getTime())
                        .status(RequestLogEnum.UNKNOWN.getCode())
                        .build();
                update[0] = requestLogMapper.updateRequestLog(updateRquestLog);
            });
            if (update[0] < 1) {
                LogUtils.error(this.getClass(), "requestLog table status : 9 update fail, guid : " + String.valueOf(guid));
            }
            throw new IdaxException(SystemStatusResponseEnum.INVOCATIONAPI_ERROR.getCode());
        }

        if (null != post) {


            int resultCode = (int) post.get("result");
            Integer status = resultCode == 0 ? RequestLogEnum.SUCCESS.getCode() : RequestLogEnum.FAILURE.getCode();
            //call remote failure, update request log, set status = 2
            if (status.equals(RequestLogEnum.FAILURE.getCode())) {
                Map<String, Object> finalPost = post;
                transactionSubmit(s -> {
                    RequestLog updateRquestLog = RequestLog.builder()
                            .GUID(guid)
                            .responseJson(JsonUtil.writeValue(finalPost))
                            .updateTime(CalendarUtil.getUTCDate().getTime())
                            .status(status)
                            .build();
                    update[0] = requestLogMapper.updateRequestLog(updateRquestLog);
                });

                throw new IdaxException(SystemStatusResponseEnum.INVOCATIONAPI_ERROR.getCode());
            }

            //call remote success, update request log, set status = 1
            RequestLog updateRquestLog = RequestLog.builder()
                    .GUID(guid)
                    .responseJson(JsonUtil.writeValue(post))
                    .updateTime(CalendarUtil.getUTCDate().getTime())
                    .status(status)
                    .build();
            update[0] = requestLogMapper.updateRequestLog(updateRquestLog);

            if (update[0] < 1) {
                LogUtils.error(this.getClass(), "requestLog table status : " + String.valueOf(status) + " update fail,guid : " + String.valueOf(guid));
                throw new IdaxException(SystemStatusResponseEnum.INVOCATIONAPI_ERROR.getCode());
            }

        } else {
            LogUtils.error(this.getClass(), "call requestAPI result is null, guid : " + String.valueOf(guid));
            throw new IdaxException(SystemStatusResponseEnum.INVOCATIONAPI_ERROR.getCode());
        }

        return true;
    }

    /**
     * insertRequestLog
     *
     * @return
     */
    public Boolean insertRequestLog(CapitalAPIParamsDTO capitalAPIParamsDTO) {

        LogUtils.info(this.getClass(), "insertRequestLogReq parmas " + JSON.toJSONString(capitalAPIParamsDTO));
        RequestLog initRequestLog = RequestLog.builder()
                .GUID(capitalAPIParamsDTO.getGuid())
                .txId(capitalAPIParamsDTO.getCapitalAPIEnum().getTxId())
                .txName(capitalAPIParamsDTO.getCapitalAPIEnum().getTxName())
                .requestJson(capitalAPIParamsDTO.getRequestJson())
                .reverseJson(capitalAPIParamsDTO.getReverseJson())
                .status(RequestLogEnum.CREATE.getCode())
                .remark("OTC->" + capitalAPIParamsDTO.getCapitalAPIEnum().getTxName())
                .createTime(CalendarUtil.getUTCDate().getTime())
                .updateTime(CalendarUtil.getUTCDate().getTime())
                .sourceId(capitalAPIParamsDTO.getChangeListData().getSourceId())
                .route("api/v1" + capitalAPIParamsDTO.getCapitalAPIEnum().getApiUrl())
                .processType(capitalAPIParamsDTO.getCapitalAPIEnum().getProcessType())
                .build();
        int insert = requestLogMapper.insertRequestLog(initRequestLog);

        LogUtils.info(this.getClass(), "insertRequestLogResult insert total " + insert + "insert data " + JSON.toJSONString(initRequestLog));
        return insert > 0;
    }

    /**
     * 适用于非下单服务（取消，过期，放币）
     * 该方法始终返回true，即使调用核心，资金划转失败，会由补偿服务帮忙做资金的划转，如果补偿服务做不成功，会由人工介入
     * 本地逻辑直接更新订单状态
     *
     * @return
     */
    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED, rollbackFor = {RuntimeException.class, Exception.class})
    public Boolean excludeCreateOrderRequestAPI(CapitalAPIParamsDTO capitalAPIParamsDTO) {

        LogUtils.info(this.getClass(), "excludeCreateOrderRequestAPI req:" + capitalAPIParamsDTO.getRequestJson());

        if(capitalAPIParamsDTO.getCapitalAPIEnum().getTxId().equals(CapitalAPIEnum.COMPLETE_ORDER.getTxId())){
            // 反转对象
            TransferCapitalDTO transferCapitalDTO = JSONObject.parseObject(capitalAPIParamsDTO.getRequestJson(), TransferCapitalDTO.class);
            // 买
            ChangeListData buyerChangeData = new ChangeListData();
            BeanUtils.copyProperties(transferCapitalDTO.getBuyerAccount(),buyerChangeData);
            accountCapitalService.changeAccount(buyerChangeData, capitalAPIParamsDTO.getCapitalAPIEnum());
            // 卖
            ChangeListData sellerChangeData = new ChangeListData();
            BeanUtils.copyProperties(transferCapitalDTO.getSellerAccount(),sellerChangeData);
            accountCapitalService.changeAccount(sellerChangeData, capitalAPIParamsDTO.getCapitalAPIEnum());
        }else{
            // 反转对象
            ChangeAccountDTO changeAccountDTO = JSONObject.parseObject(capitalAPIParamsDTO.getRequestJson(), ChangeAccountDTO.class);
            accountCapitalService.changeAccount(changeAccountDTO, capitalAPIParamsDTO.getCapitalAPIEnum());
        }

        //更新狀態 + response
        RequestLog updateRquestLog = RequestLog.builder()
                .GUID(capitalAPIParamsDTO.getGuid())
                .responseJson("")
                .updateTime(CalendarUtil.getUTCDate().getTime())
                .status(RequestLogEnum.SUCCESS.getCode())
                .build();
        requestLogMapper.updateRequestLog(updateRquestLog);
        return true;

    }

    public void transactionSubmit(Consumer consumer) {
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        // 事物隔离级别，开启新事务。
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        // 获得事务状态
        TransactionStatus status = transactionManager.getTransaction(def);
        try {
            consumer.accept(null);
            transactionManager.commit(status);
        } catch (Exception e) {
            transactionManager.rollback(status);
            throw e;
        }
    }

    public void initRequestLog(Long guid, CapitalAPIEnum capitalAPIEnum, String requestJson, String reverseJson, Integer sourceId) {
        RequestLog initRequestLog = RequestLog.builder()
                .GUID(guid)
                .txId(capitalAPIEnum.getTxId())
                .txName(capitalAPIEnum.getTxName())
                .requestJson(requestJson)
                .reverseJson(reverseJson)
                .status(RequestLogEnum.CREATE.getCode())
                .remark("OTC->" + capitalAPIEnum.getTxName())
                .createTime(CalendarUtil.getUTCDate().getTime())
                .updateTime(CalendarUtil.getUTCDate().getTime())
                .sourceId(sourceId)
                .route("api/v1" + capitalAPIEnum.getApiUrl())
                .processType(capitalAPIEnum.getProcessType())
                .build();
        if (requestLogMapper.insertRequestLog(initRequestLog) <= 0) {
            throw new RuntimeException("insert request log error");
        }
    }

    /**
     * 获取guid
     *
     * @param capitalAPIEnum
     * @return
     */
    public long getGuidByRedis(CapitalAPIEnum capitalAPIEnum) {
        long serialiNumber = 1;
        if (redisTemplate.opsForValue().get(GUID_KEY) == null) {
            redisTemplate.opsForValue().set(GUID_KEY, "1", 72, TimeUnit.HOURS);
        } else {
            serialiNumber = redisTemplate.opsForValue().increment(GUID_KEY, 1);
        }
        return UUidUtils.getGuid(capitalAPIEnum, serialiNumber);
    }

}
